相关子查询 & EXISTS

一个任务、一组数据、一条主线讲透两个最难啃的子查询知识点。 左右分栏对照:父查询在左,子查询判断在右,一屏看懂。

🎯 目标任务:查找选修了课程 c1 的学生

一、相关子查询:父查询换一行,子查询跟着重算

核心思想:代入法——把父查询当前行的值代进子查询

像初中代数一样,把值代进去

给你式子 y = 2x + 1x = 1, 2, 3y 是多少?

x = 1 → y = 2×1 + 1 = 3 x = 2 → y = 2×2 + 1 = 5 x = 3 → y = 2×3 + 1 = 7
相关子查询做的事一模一样

子查询里的 s.sno 就是那个 x。父查询每拿一行,s.sno 就换一次值,子查询就重算一次。

SELECT cno FROM sc WHERE sno = s.sno s.sno = 's1' → 查 s1 选了哪些课 s.sno = 's2' → 查 s2 选了哪些课 s.sno = 's3' → 查 s3 选了哪些课
定义
子查询的 WHERE用到了父查询当前行的字段(比如 s.sno),这个子查询就叫相关子查询。 "相关"的意思:子查询的结果依赖外层的当前行

📝 任务:查询选修了课程 c1 的学号和姓名

SQL(用相关子查询实现)
SELECT sno, sn FROM s WHERE 'c1' IN ( SELECT cno FROM sc WHERE sno = s.sno -- 这里的 s.sno 会被"代入"成具体学号 );

🎬 动态演示 点学号或按「下一步」

当前处理学生:
父查询学生表 s(当前看的这一行)
外层判断这一行要不要进结果集
代入后子查询实际执行的是
扫描中选课表 sc(全表)
结果子查询返回的课程号集合
📦 已累积的结果集(处理到当前行为止)
💬 一句话总结
上面动画里,黑底代码每换一次学生,s.sno 就变成一个新的具体学号。这就是"相关子查询"四个字的全部含义——子查询的结果和父查询当前行紧密相关。

二、EXISTS:不看返回什么,只看"有没有"

核心思想:点名册——我只问"你来没来",不问"你考了几分"

EXISTS 问的是不同的问题

前面所有子查询都在关心"返回什么值":课程号是啥?分数多少?

EXISTS 问的是更简单的问题:子查询结果里有没有记录?

有记录 → TRUE | 没记录 → FALSE | 记录里是什么,完全不关心

IN
你考了 几分?课程号是 哪个
——要读取具体的值
EXISTS
来了吗?点名册上有这号人吗?
——只看有没有

正因为只看"有没有",子查询里 SELECT 什么都一样

SELECT *✓ 常用
SELECT 1✓ 相同
SELECT 'x'✓ 相同
SELECT sno✓ 相同

EXISTS 根本不读这些值,它只数返回了几行。习惯上写 SELECT *,表示"随便选"。

📝 同一个任务:查询选修了课程 c1 的学号和姓名

现在换成 EXISTS 写。注意看 WHERE 里条件怎么变的。

SQL(用 EXISTS 实现)
SELECT sno, sn FROM s WHERE EXISTS ( SELECT * FROM sc WHERE sno = s.sno AND cno = 'c1' -- 把条件收紧到"这个学生且选了c1" );

🎬 EXISTS 动态演示 只看有没有

当前处理学生:
父查询学生表 s(当前看的这一行)
外层判断这一行要不要进结果集
代入后子查询实际执行的是
扫描中选课表 sc(全表)
核心问题子查询有没有返回记录
EXISTS 只问一件事:上面有返回行吗?
📦 已累积的结果集(处理到当前行为止)

🥊 IN 和 EXISTS,思维方向完全相反

IN 的思路

"'c1' 这个值,在不在子查询返回的那堆课程号里面?"
  • 子查询要真的读取 cno 的值
  • 返回一个集合,比如 {c1, c2}
  • 再检查 'c1' 是不是这个集合里的一员
  • 方向:值 → 集合

EXISTS 的思路

"对这个学生,能不能找到一条 cno='c1' 的记录?"
  • 子查询把条件收紧到 cno='c1'
  • 只要能查到至少一行,就为真
  • 根本不读值,只看行数是不是 > 0
  • 方向:条件 → 存在性
🧠 两节课一句话收尾
相关子查询 = 把父查询的值代入子查询,逐行重算;
EXISTS = 只问子查询"有没有",不问"是什么"。
两者是天然搭档:EXISTS 必须要父查询当前行的值来"试探",所以它几乎总是跟相关子查询一起用。